using System;
using System.Collections.Generic;
using System.Text;

// string encoding complete

namespace csammisrun.OscarLib.Utility
{
  /// <summary>
  /// Provides static methods for processing messages in SNAC family 0x0015 -- ICQ-specific functions
  /// </summary>
  /// <remarks>Good news, everyone! ICQ-specific messages use little-endian byte storage,
  /// making every existing marshaling function completely irrelevant. Note that I'm extending the
  /// custom Marshal functions instead of using the BitConverter class, because I don't know how
  /// it would behave on systems where Mono is compiled to big-endian processors. I doubt this
  /// will ever be a problem, but what the hell, basically no trouble for me.</remarks>
  internal static class SNAC15
  {
    /// <summary>
    /// Requests that the server send any offline messages -- SNAC(15,02)
    /// </summary>
    /// <param name="sess">A <see cref="Session"/> object</param>
    public static void RequestOfflineMessages(Session sess)
    {
      SNACHeader sh = new SNACHeader();
      sh.FamilyServiceID = (ushort)SNACFamily.ICQExtensionsService;
      sh.FamilySubtypeID = (ushort)ICQExtensionsService.MetaInformationRequest;
      sh.Flags = 0x0000;
      sh.RequestID = sess.GetNextRequestID();

      int index = 0;
      byte[] buffer = new byte[2 + 2 + 2 + 2 + 4 + 2];
      Marshal.InsertUshort(buffer, 0x0001, ref index);
      Marshal.InsertUshort(buffer, (ushort)(buffer.Length - 4), ref index);

      MarshalLE.InsertUShort(buffer, (ushort)(buffer.Length - 6), ref index);
      MarshalLE.InsertUint(buffer, uint.Parse(sess.ScreenName), ref index);
      MarshalLE.InsertUShort(buffer, 0x003C, ref index);
      MarshalLE.InsertUShort(buffer, (ushort)sh.RequestID, ref index);

      DataPacket dp = Marshal.BuildDataPacket(sess, sh, buffer);
      SNACFunctions.BuildFLAP(dp);
    }

    /// <summary>
    /// Deletes stored offline messages from the server -- SNAC(15,02)
    /// </summary>
    /// <param name="sess">A <see cref="Session"/> object</param>
    public static void DeleteOfflineMessages(Session sess)
    {
      SNACHeader sh = new SNACHeader();
      sh.FamilyServiceID = (ushort)SNACFamily.ICQExtensionsService;
      sh.FamilySubtypeID = (ushort)ICQExtensionsService.MetaInformationRequest;
      sh.Flags = 0x0000;
      sh.RequestID = sess.GetNextRequestID();

      int index = 0;
      byte[] buffer = new byte[2 + 2 + 2 + 2 + 4 + 2];
      Marshal.InsertUshort(buffer, 0x0001, ref index);
      Marshal.InsertUshort(buffer, (ushort)(buffer.Length - 4), ref index);

      MarshalLE.InsertUShort(buffer, (ushort)(buffer.Length - 6), ref index);
      MarshalLE.InsertUint(buffer, uint.Parse(sess.ScreenName), ref index);
      MarshalLE.InsertUShort(buffer, 0x003E, ref index);
      MarshalLE.InsertUShort(buffer, (ushort)sh.RequestID, ref index);

      DataPacket dp = Marshal.BuildDataPacket(sess, sh, buffer);
      SNACFunctions.BuildFLAP(dp);
    }

    /// <summary>
    /// Hides the client's IP address from view
    /// </summary>
    /// <param name="sess">A <see cref="Session"/> object</param>
    public static void HidePublicIP(Session sess)
    {
      SNACHeader sh = new SNACHeader();
      sh.FamilyServiceID = (ushort)SNACFamily.ICQExtensionsService;
      sh.FamilySubtypeID = (ushort)ICQExtensionsService.MetaInformationRequest;
      sh.Flags = 0x0000;
      sh.RequestID = sess.GetNextRequestID();

      int index = 0;
      byte[] buffer = new byte[2 + 2 + 2 + 4 + 2 + 2 + 2 + 2 + 2];

      Marshal.InsertUshort(buffer, 0x0001, ref index);
      Marshal.InsertUshort(buffer, (ushort)(buffer.Length - 4), ref index);
      MarshalLE.InsertUShort(buffer, (ushort)(buffer.Length - 6), ref index);
      MarshalLE.InsertUint(buffer, uint.Parse(sess.ScreenName), ref index);

      MarshalLE.InsertUShort(buffer, 0x07D0, ref index);
      MarshalLE.InsertUShort(buffer, (ushort)sh.RequestID, ref index);
      MarshalLE.InsertUShort(buffer, 0x0424, ref index);
      MarshalLE.InsertUShort(buffer, 0x0001, ref index);
      MarshalLE.InsertUShort(buffer, 0x0001, ref index);

      DataPacket dp = Marshal.BuildDataPacket(sess, sh, buffer);
      SNACFunctions.BuildFLAP(dp);
    }

    /// <summary>
    /// Changes the password on an ICQ account
    /// </summary>
    /// <param name="sess">A <see cref="Session"/> object</param>
    /// <param name="newpassword">The new password</param>
    /// <remarks>If the new password is longer than 8 characters, it is automatically
    /// truncated to 8 characters by the server.</remarks>
    public static void ChangeICQPassword(Session sess, string newpassword)
    {
      SNACHeader sh = new SNACHeader();
      sh.FamilyServiceID = (ushort)SNACFamily.ICQExtensionsService;
      sh.FamilySubtypeID = (ushort)ICQExtensionsService.MetaInformationRequest;
      sh.Flags = 0x0000;
      sh.RequestID = sess.GetNextRequestID();

      int index = 0;
      byte[] buffer = new byte[2 + 2 + 2 + 4 + 2 + 2 + 2 + 2 + newpassword.Length + 1];

      Marshal.InsertUshort(buffer, 0x0001, ref index);
      Marshal.InsertUshort(buffer, (ushort)(buffer.Length - 4), ref index);
      MarshalLE.InsertUShort(buffer, (ushort)(buffer.Length - 6), ref index);
      MarshalLE.InsertUint(buffer, uint.Parse(sess.ScreenName), ref index);

      MarshalLE.InsertUShort(buffer, 0x07D0, ref index);
      MarshalLE.InsertUShort(buffer, (ushort)sh.RequestID, ref index);
      MarshalLE.InsertUShort(buffer, 0x042E, ref index);
      MarshalLE.InsertUShort(buffer, (ushort)(newpassword.Length + 1), ref index);
      Marshal.InsertString(buffer, newpassword, Marshal.ASCII, ref index);
      buffer[index++] = 0x00;

      DataPacket dp = Marshal.BuildDataPacket(sess, sh, buffer);
      SNACFunctions.BuildFLAP(dp);
    }

    /// <summary>
    /// Requests a full set of information about an ICQ account
    /// </summary>
    /// <param name="sess">A <see cref="Session"/> object</param>
    /// <param name="screenname">The account for which to retrieve information</param>
    public static void GetAllICQInfo(Session sess, string screenname)
    {
      if (!ScreennameVerifier.IsValidICQ(screenname))
      {
        throw new ArgumentException(screenname + " is not a valid ICQ screenname", "screenname");
      }

      SNACHeader sh = new SNACHeader();
      sh.FamilyServiceID = (ushort)SNACFamily.ICQExtensionsService;
      sh.FamilySubtypeID = (ushort)ICQExtensionsService.MetaInformationRequest;
      sh.Flags = 0x0000;
      sh.RequestID = sess.GetNextRequestID();

      int index = 0;
      byte[] buffer = new byte[2 + 2 + 2 + 4 + 2 + 2 + 2 + 4];

      Marshal.InsertUshort(buffer, 0x0001, ref index);
      Marshal.InsertUshort(buffer, (ushort)(buffer.Length - 4), ref index);
      MarshalLE.InsertUShort(buffer, (ushort)(buffer.Length - 6), ref index);
      MarshalLE.InsertUint(buffer, uint.Parse(sess.ScreenName), ref index);

      MarshalLE.InsertUShort(buffer, 0x07D0, ref index);
      MarshalLE.InsertUShort(buffer, (ushort)sh.RequestID, ref index);
      MarshalLE.InsertUShort(buffer, 0x04B2, ref index);
      MarshalLE.InsertUint(buffer, uint.Parse(screenname), ref index);

      DataPacket dp = Marshal.BuildDataPacket(sess, sh, buffer);
      SNACFunctions.BuildFLAP(dp);
    }

    /// <summary>
    /// Requests a minimal set of information about an ICQ account
    /// </summary>
    /// <param name="sess">A <see cref="Session"/> object</param>
    /// <param name="screenname">The account for which to retrieve information</param>
    public static void GetSimpleICQInfo(Session sess, string screenname)
    {
      if (!ScreennameVerifier.IsValidICQ(screenname))
      {
        throw new ArgumentException(screenname + " is not a valid ICQ screenname", "screenname");
      }

      SNACHeader sh = new SNACHeader();
      sh.FamilyServiceID = (ushort)SNACFamily.ICQExtensionsService;
      sh.FamilySubtypeID = (ushort)ICQExtensionsService.MetaInformationRequest;
      sh.Flags = 0x0000;
      sh.RequestID = sess.GetNextRequestID();

      int index = 0;
      byte[] buffer = new byte[2 + 2 + 2 + 4 + 2 + 2 + 2 + 4];

      Marshal.InsertUshort(buffer, 0x0001, ref index);
      Marshal.InsertUshort(buffer, (ushort)(buffer.Length - 4), ref index);
      MarshalLE.InsertUShort(buffer, (ushort)(buffer.Length - 6), ref index);
      MarshalLE.InsertUint(buffer, uint.Parse(sess.ScreenName), ref index);

      MarshalLE.InsertUShort(buffer, 0x07D0, ref index);
      MarshalLE.InsertUShort(buffer, (ushort)sh.RequestID, ref index);
      MarshalLE.InsertUShort(buffer, 0x051F, ref index);
      MarshalLE.InsertUint(buffer, uint.Parse(screenname), ref index);

      DataPacket dp = Marshal.BuildDataPacket(sess, sh, buffer);
      SNACFunctions.BuildFLAP(dp);
    }

    /// <summary>
    /// Requests the alias assigned to an ICQ account
    /// </summary>
    /// <param name="sess">A <see cref="Session"/> object</param>
    /// <param name="screenname">The account for which to retrieve information</param>
    public static void GetAlias(Session sess, string screenname)
    {
      if (!ScreennameVerifier.IsValidICQ(screenname))
      {
        throw new ArgumentException(screenname + " is not a valid ICQ screenname", "screenname");
      }

      SNACHeader sh = new SNACHeader();
      sh.FamilyServiceID = (ushort)SNACFamily.ICQExtensionsService;
      sh.FamilySubtypeID = (ushort)ICQExtensionsService.MetaInformationRequest;
      sh.Flags = 0x0000;
      sh.RequestID = sess.GetNextRequestID();

      int index = 0;
      byte[] buffer = new byte[2 + 2 + 2 + 4 + 2 + 2 + 2 + 4];

      Marshal.InsertUshort(buffer, 0x0001, ref index);
      Marshal.InsertUshort(buffer, (ushort)(buffer.Length - 4), ref index);
      MarshalLE.InsertUShort(buffer, (ushort)(buffer.Length - 6), ref index);
      MarshalLE.InsertUint(buffer, uint.Parse(sess.ScreenName), ref index);

      MarshalLE.InsertUShort(buffer, 0x07D0, ref index);
      MarshalLE.InsertUShort(buffer, (ushort)sh.RequestID, ref index);
      MarshalLE.InsertUShort(buffer, 0x04BA, ref index);
      MarshalLE.InsertUint(buffer, uint.Parse(screenname), ref index);

      DataPacket dp = Marshal.BuildDataPacket(sess, sh, buffer);
      SNACFunctions.BuildFLAP(dp);
    }

    /// <summary>
    /// Sends an XML string
    /// </summary>
    /// <param name="sess">A <see cref="Session"/> object</param>
    /// <param name="xml">The contents of an XML document</param>
    /// <remarks>I have no idea how to use this.</remarks>
    public static void SendXmlRequest(Session sess, string xml)
    {
      SNACHeader sh = new SNACHeader();
      sh.FamilyServiceID = (ushort)SNACFamily.ICQExtensionsService;
      sh.FamilySubtypeID = (ushort)ICQExtensionsService.MetaInformationRequest;
      sh.Flags = 0x0000;
      sh.RequestID = sess.GetNextRequestID();

      int index = 0;
      Encoding enc = Marshal.ASCII;
      ushort xmllength = (ushort)(enc.GetByteCount(xml) + 1);
      byte[] buffer = new byte[2 + 2 + 2 + 4 + 2 + 2 + 2 + 2 + xmllength];

      Marshal.InsertUshort(buffer, 0x0001, ref index);
      Marshal.InsertUshort(buffer, (ushort)(buffer.Length - 4), ref index);
      MarshalLE.InsertUShort(buffer, (ushort)(buffer.Length - 6), ref index);
      MarshalLE.InsertUint(buffer, uint.Parse(sess.ScreenName), ref index);

      MarshalLE.InsertUShort(buffer, 0x07D0, ref index);
      MarshalLE.InsertUShort(buffer, (ushort)sh.RequestID, ref index);
      MarshalLE.InsertUShort(buffer, 0x0998, ref index);
      MarshalLE.InsertUShort(buffer, xmllength, ref index);
      Marshal.InsertString(buffer, xml, enc, ref index);
      buffer[index++] = 0x00;

      DataPacket dp = Marshal.BuildDataPacket(sess, sh, buffer);
      SNACFunctions.BuildFLAP(dp);
    }

    /// <summary>
    /// Sends an out-of-the-country text message
    /// </summary>
    /// <param name="sess">A <see cref="Session"/> object</param>
    /// <param name="number">The number to which to send the message</param>
    /// <param name="message">The message to send</param>
    /// <param name="alias">The sender's alias</param>
    /// <remarks><paramref name="message"/> must be in codepage 1252. A delivery receipt
    /// is automatically requested by this method.</remarks>
    public static void SendSMSMessage(Session sess, string number, string message, string alias)
    {
      string xmlformat = "<icq_sms_message>\n" +
        "\t<destination>{0}</destination>\n" +
        "\t<text>{1}</text>\n" +
        "\t<codepage>1252</codepage>\n" +
        "\t<senders_UIN>{2}</senders_UIN>\n" +
        "\t<senders_name>{3}</senders_name>\n" +
        "\t<delivery_receipt>Yes</delivery_receipt>\n" +
        "\t<time>{4}</time>\n" +
        "</icq_sms_message>\n";

      string xml = String.Format(xmlformat,
        number,
        message,
        sess.ScreenName,
        alias,
        DateTime.Now.ToString("r"));

      SNACHeader sh = new SNACHeader();
      sh.FamilyServiceID = (ushort)SNACFamily.ICQExtensionsService;
      sh.FamilySubtypeID = (ushort)ICQExtensionsService.MetaInformationRequest;
      sh.Flags = 0x0000;
      sh.RequestID = sess.GetNextRequestID();

      int index = 0;
      Encoding enc = Marshal.ASCII;
      ushort xmllength = (ushort)(enc.GetByteCount(xml) + 1);
      byte[] buffer = new byte[40 + xmllength];

      
      Marshal.InsertUshort(buffer, 0x0001, ref index);
      Marshal.InsertUshort(buffer, (ushort)(buffer.Length - 4), ref index);
      MarshalLE.InsertUShort(buffer, (ushort)(buffer.Length - 6), ref index);
      MarshalLE.InsertUint(buffer, uint.Parse(sess.ScreenName), ref index);

      MarshalLE.InsertUShort(buffer, 0x07D0, ref index);
      MarshalLE.InsertUShort(buffer, (ushort)sh.RequestID, ref index);
      MarshalLE.InsertUShort(buffer, 0x8214, ref index);

      Marshal.InsertUshort(buffer, 0x0001, ref index);
      Marshal.InsertUshort(buffer, 0x0016, ref index);
      Marshal.InsertUint(buffer, 0x00000000, ref index);
      Marshal.InsertUint(buffer, 0x00000000, ref index);
      Marshal.InsertUint(buffer, 0x00000000, ref index);
      Marshal.InsertUint(buffer, 0x00000000, ref index);

      Marshal.InsertUshort(buffer, 0x0000, ref index);
      Marshal.InsertUshort(buffer, xmllength, ref index);
      Marshal.InsertString(buffer, xml, enc, ref index);
      buffer[index++] = 0x00;

      DataPacket dp = Marshal.BuildDataPacket(sess, sh, buffer);
      SNACFunctions.BuildFLAP(dp);
    }

    public static void ProcessInfoResponse(DataPacket dp)
    {
      // The first four bytes of this are the TLV 0x0001
      // header which encapsulates the rest of the data
      int index = 4;

      ushort cmdlength = MarshalLE.ByteArrayToUshort(dp.Data, ref index);
      uint my_uin = MarshalLE.ByteArrayToUint(dp.Data, ref index);
      ushort command = MarshalLE.ByteArrayToUshort(dp.Data, ref index);
      ushort requestID = MarshalLE.ByteArrayToUshort(dp.Data, ref index);

      Encoding enc = Marshal.ASCII;

      switch (command)
      {
        case 0x0041:
          break;
        case 0x0042:
          // Gaim does a callback here
          break;
        case 0x07DA:
          ICQInfo info = new ICQInfo();
          // RecallICQInfo(requestid)
          ushort subtype = MarshalLE.ByteArrayToUshort(dp.Data, ref index);
          index++; // 0x0A
          switch (subtype)
          {
            case 0x00A0:
              break;
            case 0x00AA:
              break;
            case 0x00C8:
              info.Nickname = Marshal.ByteArrayToString(dp.Data, ref index,
                MarshalLE.ByteArrayToUshort(dp.Data, ref index), enc);
              info.Firstname = Marshal.ByteArrayToString(dp.Data, ref index,
                MarshalLE.ByteArrayToUshort(dp.Data, ref index), enc);
              info.Lastname = Marshal.ByteArrayToString(dp.Data, ref index,
                MarshalLE.ByteArrayToUshort(dp.Data, ref index), enc);
              info.Email = Marshal.ByteArrayToString(dp.Data, ref index,
                MarshalLE.ByteArrayToUshort(dp.Data, ref index), enc);
              info.HomeCity = Marshal.ByteArrayToString(dp.Data, ref index,
                MarshalLE.ByteArrayToUshort(dp.Data, ref index), enc);
              info.HomeState = Marshal.ByteArrayToString(dp.Data, ref index,
                MarshalLE.ByteArrayToUshort(dp.Data, ref index), enc);
              info.HomePhone = Marshal.ByteArrayToString(dp.Data, ref index,
                MarshalLE.ByteArrayToUshort(dp.Data, ref index), enc);
              info.HomeFax = Marshal.ByteArrayToString(dp.Data, ref index,
                MarshalLE.ByteArrayToUshort(dp.Data, ref index), enc);
              info.HomeAddress = Marshal.ByteArrayToString(dp.Data, ref index,
                MarshalLE.ByteArrayToUshort(dp.Data, ref index), enc);
              info.MobilePhone = Marshal.ByteArrayToString(dp.Data, ref index,
                MarshalLE.ByteArrayToUshort(dp.Data, ref index), enc);
              info.HomeZip = Marshal.ByteArrayToString(dp.Data, ref index,
                MarshalLE.ByteArrayToUshort(dp.Data, ref index), enc);
              info.HomeCountry = MarshalLE.ByteArrayToUshort(dp.Data, ref index);
              break;
            case 0x00DC:
              info.Age = dp.Data[index++];
              index++; // unknown
              info.Gender = dp.Data[index++];
              info.PersonalURL = Marshal.ByteArrayToString(dp.Data, ref index,
                MarshalLE.ByteArrayToUshort(dp.Data, ref index), enc);
              info.BirthYear = MarshalLE.ByteArrayToUshort(dp.Data, ref index);
              info.BirthMonth = dp.Data[index++];
              info.BirthDay = dp.Data[index++];
              info.Language1 = dp.Data[index++];
              info.Language2 = dp.Data[index++];
              info.Language3 = dp.Data[index++];
              break;
            case 0x00D2:
              info.WorkCity = Marshal.ByteArrayToString(dp.Data, ref index,
                MarshalLE.ByteArrayToUshort(dp.Data, ref index), enc);
              info.WorkState = Marshal.ByteArrayToString(dp.Data, ref index,
                MarshalLE.ByteArrayToUshort(dp.Data, ref index), enc);
              info.WorkPhone = Marshal.ByteArrayToString(dp.Data, ref index,
                MarshalLE.ByteArrayToUshort(dp.Data, ref index), enc);
              info.WorkFax = Marshal.ByteArrayToString(dp.Data, ref index,
                MarshalLE.ByteArrayToUshort(dp.Data, ref index), enc);
              info.WorkAddress = Marshal.ByteArrayToString(dp.Data, ref index,
                MarshalLE.ByteArrayToUshort(dp.Data, ref index), enc);
              info.WorkZip = Marshal.ByteArrayToString(dp.Data, ref index,
                MarshalLE.ByteArrayToUshort(dp.Data, ref index), enc);
              info.WorkCountry = MarshalLE.ByteArrayToUshort(dp.Data, ref index);
              info.WorkCompany = Marshal.ByteArrayToString(dp.Data, ref index,
                MarshalLE.ByteArrayToUshort(dp.Data, ref index), enc);
              info.WorkDivision = Marshal.ByteArrayToString(dp.Data, ref index,
                MarshalLE.ByteArrayToUshort(dp.Data, ref index), enc);
              info.WorkPosition = Marshal.ByteArrayToString(dp.Data, ref index,
                MarshalLE.ByteArrayToUshort(dp.Data, ref index), enc);
              index += 2;
              info.WorkWebsite = Marshal.ByteArrayToString(dp.Data, ref index,
                MarshalLE.ByteArrayToUshort(dp.Data, ref index), enc);
              break;
            case 0x00E6:
              info.Information = Marshal.ByteArrayToString(dp.Data, ref index,
                MarshalLE.ByteArrayToUshort(dp.Data, ref index), enc);
              break;
            case 0x00EB:
              ushort numaddresses = MarshalLE.ByteArrayToUshort(dp.Data, ref index);
              info.EmailAddresses = new string[numaddresses];
              for (int i = 0; i < numaddresses; i++)
              {
                info.EmailAddresses[i] = Marshal.ByteArrayToString(dp.Data, ref index,
                  MarshalLE.ByteArrayToUshort(dp.Data, ref index), enc);
                if (i + 1 != numaddresses)
                  index++;
              }
              break;
            case 0x00F0:
              break;
            case 0x00FA:
              break;
            case 0x0104:
              info.Nickname = Marshal.ByteArrayToString(dp.Data, ref index,
                MarshalLE.ByteArrayToUshort(dp.Data, ref index), enc);
              info.Firstname = Marshal.ByteArrayToString(dp.Data, ref index,
                MarshalLE.ByteArrayToUshort(dp.Data, ref index), enc);
              info.Lastname = Marshal.ByteArrayToString(dp.Data, ref index,
                MarshalLE.ByteArrayToUshort(dp.Data, ref index), enc);

              break;
            case 0x010E:
              break;
            case 0x019A:
              index += 2;
              info.Screenname =
                MarshalLE.ByteArrayToUint(dp.Data, ref index).ToString();
              info.Nickname = Marshal.ByteArrayToString(dp.Data, ref index,
                MarshalLE.ByteArrayToUshort(dp.Data, ref index), enc);
              info.Firstname = Marshal.ByteArrayToString(dp.Data, ref index,
                MarshalLE.ByteArrayToUshort(dp.Data, ref index), enc);
              info.Lastname = Marshal.ByteArrayToString(dp.Data, ref index,
                MarshalLE.ByteArrayToUshort(dp.Data, ref index), enc);
              info.Email = Marshal.ByteArrayToString(dp.Data, ref index,
                MarshalLE.ByteArrayToUshort(dp.Data, ref index), enc);
              break;
          } // End the subtype switch statement
          break;
          
          // Do some crazy multipart snac stuff
        default:
          index += cmdlength;
          break;
      }
    }
  }
}
